use std::{sync::Mutex, time::Duration};

use log::{error, info};
use rusb::{self, Context, Device, DeviceDescriptor, Direction, UsbContext};

use crate::server::stub::{
        session::{self, StubInterface},
        usb::usb_stub::UsbStub,
    };

use super::usb_stub::Endpoint;

struct UsbManager {
    context: Mutex<Context>,
}

impl UsbManager {
    pub fn new() -> Option<Self> {
        let context = match Context::new() {
            Ok(context) => context,
            Err(err) => {
                error!("Counld not initialize libusb: {err}");
                return None;
            }
        };
        Some(Self { context: Mutex::new(context) })
    }

    pub fn scan_device(&mut self) {
        let Ok(context) = self.context.lock() else {
            return;
        };
        let devices = match context.devices() {
            Ok(d) => d,
            Err(e) => {
                error!("scan_device failed, {e}");
                return;
            }
        };

        for device in devices.iter() {
            let device_desc = match device.device_descriptor() {
                Ok(d) => d,
                Err(_) => continue,
            };

            // open
            let Ok(handle) = device.open() else {
                // error!("open failed. Id {}:{}", device_desc.vendor_id(), device_desc.product_id());
                continue;
            };

            let timeout = Duration::from_secs(1);
            // read language
            let Ok(languages) = handle.read_languages(timeout) else {
                // error!("read_languages error. Id {}:{}", device_desc.vendor_id(), device_desc.product_id());
                continue;
            };

            if !languages.is_empty() {
                let language = languages[0];
                // read sn
                let Ok(sn) = handle.read_serial_number_string(language, &device_desc, timeout) else {
                    // error!("read sn error. Id {}:{}", device_desc.vendor_id(), device_desc.product_id());
                    continue;
                };

                // scan endpoind
                let (Some(endpoint_in), Some(endpoint_out)) = find_readable_endpoint(&device, &device_desc) else {
                    continue;
                };

                ylong_runtime::block_on(async {
                    if !session::contains_key(&sn).await {
                        info!("sn {:?}", sn);

                        let usb_stub = UsbStub::new();
                        usb_stub.start_relay(handle, endpoint_in, endpoint_out);
                        let _ = session::add_session(&sn, StubInterface::UsbStub(usb_stub)).await;
                    }
                });
            }
        }
    }
}

fn find_readable_endpoint<T: UsbContext>(
    device: &Device<T>, device_desc: &DeviceDescriptor,
) -> (Option<Endpoint>, Option<Endpoint>) {
    let mut endpoint_in = None;
    let mut endpoint_out = None;
    for n in 0..device_desc.num_configurations() {
        let config_desc = match device.config_descriptor(n) {
            Ok(c) => c,
            Err(_) => continue,
        };

        for interface in config_desc.interfaces() {
            for interface_desc in interface.descriptors() {
                for endpoint_desc in interface_desc.endpoint_descriptors() {
                    match endpoint_desc.transfer_type() {
                        rusb::TransferType::Bulk if endpoint_desc.direction() == Direction::In => {
                            endpoint_in = Some(Endpoint {
                                config: config_desc.number(),
                                iface: interface_desc.interface_number(),
                                setting: interface_desc.setting_number(),
                                address: endpoint_desc.address(),
                            });
                        }
                        rusb::TransferType::Bulk if endpoint_desc.direction() == Direction::Out => {
                            endpoint_out = Some(Endpoint {
                                config: config_desc.number(),
                                iface: interface_desc.interface_number(),
                                setting: interface_desc.setting_number(),
                                address: endpoint_desc.address(),
                            });
                        }
                        _ => {}
                    };
                }
            }
        }
    }

    (endpoint_in, endpoint_out)
}

pub fn usb_backend_daemon() {
    info!("usb_backend_daemon()");
    let Some(mut usb_manager) = UsbManager::new() else {
        return;
    };

    loop {
        usb_manager.scan_device();

        std::thread::sleep(Duration::from_secs(10));
    }
}
